#include "scripting/script_translator.h"
#include "scripting/script_lexer.h"
#include "scripting/script_parser.h"
#include "scripting/script_semantic_analyzer.h"
#include "utils/utils/string_util.h"
#include "utils/utils/file.h"
#include "scripting/script_interpreter.h"
#include <filesystem>

namespace script
{
class ScriptInterpreterData
{
private:
    friend class ScriptInterpreter;
    ScriptTranslator script_translator;
    ErrorInfo error_info;
    std::string source_code;
    std::string work_path;

    /**
     * @brief:  保存词法分析/语义分析/语法分析/语法翻译的错误信息
     * @author: sherlock_lht
     */
    void saveLexerError(const ScriptLexer &script_lexer);
    void saveParserError(const ScriptParser &script_parser);
    void saveSemanticAnalyzerError(const ScriptSemanticAnalyzer &semantic_analyzer);
    void saveTranslatorError();

    /**
     * @brief:  获取源码中指定的某一行代码
     * @author: sherlock_lht
     */
    std::string getLineCodeByLine(int line) const;
};
void ScriptInterpreterData::saveLexerError(const ScriptLexer &script_lexer)
{
    script_lexer.getErrorInfo(&error_info.line, &error_info.column, &error_info.error_message);
    error_info.line_code = std::move(getLineCodeByLine(error_info.line));
}
void ScriptInterpreterData::saveParserError(const ScriptParser &script_parser)
{
    Token error_token;
    script_parser.getErrorInfo(&error_token, &error_info.error_message);
    error_info.line = error_token.line;
    error_info.column = error_token.column;
    error_info.line_code = std::move(getLineCodeByLine(error_info.line));
}
void ScriptInterpreterData::saveSemanticAnalyzerError(const ScriptSemanticAnalyzer &semantic_analyzer)
{
    error_info = std::move(semantic_analyzer.getErrorInfo());
    error_info.line_code = std::move(getLineCodeByLine(error_info.line));
}
void ScriptInterpreterData::saveTranslatorError()
{
    script_translator.getLastInfo(&error_info.line, &error_info.column, &error_info.error_message);
    error_info.line_code = std::move(getLineCodeByLine(error_info.line));
}
std::string ScriptInterpreterData::getLineCodeByLine(int line) const
{
    std::vector<std::string> code_line = base::utils::StringUtil::split(source_code, "\n");
    if (line >= code_line.size())
    {
        return {};
    }
    return code_line.at(line);
}
}

namespace script
{
ScriptInterpreter::ScriptInterpreter()
    : d(std::make_unique<ScriptInterpreterData>())
{
    d->work_path = std::filesystem::current_path().string();
}
ScriptInterpreter::~ScriptInterpreter() = default;

std::optional<scheduler::AbstractSchedulerConstPtr> ScriptInterpreter::interpret(const std::string &code)
{
    d->source_code = code;
    //词法分析
    ScriptLexer script_lexer;
    auto token = script_lexer.scan(code);
    if (!token)
    {
        d->saveLexerError(script_lexer);
        return std::nullopt;
    }
    //语义分析
    ScriptParser script_parser;
    auto ast_node = script_parser.parse(token.value());
    if (!ast_node)
    {
        d->saveParserError(script_parser);
        return std::nullopt;
    }
    //语法分析
    ScriptSemanticAnalyzer semantic_analyzer;
    if (!semantic_analyzer.analyze(ast_node.value()))
    {
        d->saveSemanticAnalyzerError(semantic_analyzer);
        return std::nullopt;
    }
    //语法翻译
    auto root_scheduler = d->script_translator.translate(ast_node.value());
    if (!root_scheduler)
    {
        d->saveTranslatorError();
        return std::nullopt;
    }
    return root_scheduler;
}
std::optional<scheduler::AbstractSchedulerConstPtr> ScriptInterpreter::interpretSourceFile(const std::string &file_name)
{
    std::filesystem::path file_path = file_name;
    if (file_path.is_relative())
    {
        file_path = d->work_path / file_path;
    }
    EASE_DEBUG("interpret source file: %s", file_path.c_str());
    if (!exists(file_path))
    {
        d->error_info.line = 0;
        d->error_info.column = 0;
        d->error_info.error_message = file_path.string() + " cannot be found";
        return std::nullopt;
    }
    base::utils::File file(file_path.string());
    if (!file.open(std::ios_base::in))
    {
        d->error_info.line = 0;
        d->error_info.column = 0;
        d->error_info.error_message = file_path.string() + " open fail";
        return std::nullopt;
    }
    std::string code = file.readAll();
    file.close();
    return interpret(code);
}
ErrorInfo ScriptInterpreter::getErrorInfo() const
{
    return d->error_info;
}
void ScriptInterpreter::setWorkPath(const std::string &path)
{
    std::filesystem::path work_path(path);
    if (!is_directory(work_path))
    {
        EASE_WARN("set work path fail, %s is not directory", path.c_str());
        return;
    }
    if (!std::filesystem::exists(work_path))
    {
        EASE_WARN("set work path fail, %s is not exists", path.c_str());
        return;
    }
    d->work_path = path;
}

}
